package com.example.mq.mqserver.core;

import com.example.mq.common.BinaryTool;
import com.example.mq.common.MqException;

/**
 * 实现交换机的转发规则
 * 验证 bindingKey 是否合法
 */
public class Router {

    public boolean checkBindingKey(String bindingKey){
        if(0 == bindingKey.length()){
            // 空字符串，合法情况，在使用 DIRECT / FANOUT 交换机的时候用不到 bindingKey ,可以设为 ""
            return true;
        }
        // 检查字符串中不能存在非法字符
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            // 判定该字符串是否是 大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }

            // 判定该字符串是否是 小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }

            // 判定该字符串是否是 阿拉伯数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }

            // 判定是否是 _ 或者 . 或者 * 或者 #
            if (ch == '-' || ch == '.' || ch == '*' || ch == '#') {
                continue;
            }
            return false;
        }
        // 检查 * 或者 # 是否是独立的部分
        // aaa.*.bbb -> 合法
        // aaa.a*.bbb -> 非法
        String[] words = bindingKey.split("\\.");
        for (String word : words) {
            // 检查 word 长度 > 1 并且包含 * 或者 # -> 非法
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }
        // 约定通配符之间的相邻关系
        // 1. aaa.#.#.bbb -> 非法
        // 2. aaa.#.*.bbb -> 非法
        // 3. aaa.*.#.bbb -> 非法
        // 4. aaa.*.*.bbb -> 合法
        for (int i = 0; i < words.length - 1; i++) {
            // # 连着 #
            if (words[i].equals("#") && words[i + 1].equals("#")) {
                return false;
            }

            // # 连着 *
            if (words[i].equals("#") && words[i + 1].equals("*")) {
                return false;
            }

            // * 连着 #
            if (words[i].equals("*") && words[i + 1].equals("#")) {
                return false;
            }
        }
        return true;
    }

    /**
     * routingKey 的构造规则
     * 1. 数字、字母、下划线
     * 2. 使用 . 分割成若干部分
     * @param routingKey
     * @return
     */
    public boolean checkRoutingKey(String routingKey){
        if(0 == routingKey.length()){
            // 空字符串，合法情况，FANOUT 交换机用不上 routingKey ,可以设置为 ""
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            // 判定该字符串是否是 大写字母
            if(ch >= 'A' && ch <= 'Z'){
                continue;
            }

            // 判定该字符串是否是 小写字母
            if(ch >= 'a' && ch <= 'z'){
                continue;
            }

            // 判定该字符串是否是 阿拉伯数字
            if(ch >= '0' && ch <= '9'){
                continue;
            }

            // 判定是否是 _ 或者 .
            if(ch == '-' || ch == '.'){
                continue;
            }

            // 不是上述任何一种情况，非法 routingKey
            return false;
        }
        // routingKey 合法
        return true;
    }

    // 用来判定该消息是否可以转发这个绑定对应的队列
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MqException {
        // 根据不同的 exchangeType 使用不同的判定转发规则
        if(exchangeType == ExchangeType.FANOUT){
            // 如果类型为 FANOUT 类型，则交换机上绑定的所有队列都需要转发
            return true;
        }else if(exchangeType == ExchangeType.TOPIC){
            // 类型为 TOPIC
            return routeTopic(binding, message);
        }else{
            // 前面处理过 类型为 DIRECT 的情况了，所以其他情况是不应该存在的
            throw new MqException("[Router] 交换机类型非法！ exchangeType = " + exchangeType);
        }
    }

    private boolean routeTopic(Binding binding, Message message) {
        // 先把两个 key 进行拆分
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");

        // 引入两个下标
        int bindingIndex = 0;
        int routingIndex = 0;
        while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length){
            if(bindingTokens[bindingIndex].equals("*")){
                // 如果遇到 * ,两个下标都加一
                bindingIndex++;
                routingIndex++;
                continue;
            }else if(bindingTokens[bindingIndex].equals("#")){
                // 如果 bindingKey 遇到 # ，要看是不是结尾
                bindingIndex++;
                if(bindingIndex == bindingTokens.length){
                    // # 为结尾
                    return true;
                }
                // # 后面还有东西，根据这个内容去 routingKey 中往后查找这个内容，看是否有这个内容，查到这段内容的下标
                // findNextMatch 这个方法用来查找该部分在 routingKey 的位置，返回该下标，没找到返回 -1
                routingIndex = findNextMatch(routingTokens, routingIndex, bindingTokens[bindingIndex]);
                if(routingIndex == -1){
                    // 没找到匹配的结果
                    return false;
                }
                // 找到匹配的情况，bindingKey 指向 # 的下一个位置，routingKey 指向对应内容的位置，即 routingIndex 的位置
                // 两个下标指向内容相同，都从下一个位置开始继续匹配，都加 1
                bindingIndex++;
                routingIndex++;
            }else{
                // 遇到的是普通字符串，要求两边内容一样
                if(!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])){
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
        // 判定是否双方同时到达末尾
        // 比如 aaa.bbb.ccc 和 aaa.bbb 是匹配失败的
        if(bindingIndex == bindingTokens.length && routingIndex == routingTokens.length){
            return true;
        }
        return false;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            if(routingTokens[i].equals(bindingToken)){
                return i;
            }
        }
        return -1;
    }
}
